-
Notifications
You must be signed in to change notification settings - Fork 0
Separate platform api, major refactor #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…clude` directory and tidied up `CMakeLists.txt`
WalkthroughAdds a modular game engine: new Game, GameState, Player, World, Renderer, input and SDL platform layers, asset management, and refactored build (CMake find_package, asset copy, sanitizers). Removes legacy core world/header and old main; introduces platform entrypoint and .clang-format. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Main as src/platform/main.cpp
participant Game as Game
participant Input as InputManager
participant Renderer as Renderer
participant GD as GraphicsDevice
participant State as GameState
participant World as World
User->>Main: start process
Main->>Game: construct + init()
Game->>Renderer: init(winW,winH,title)
Renderer->>GD: init(...)
GD-->>Renderer: success/fail
Game->>Input: init()
Game->>World: init()
World-->>Game: tiles populated
loop while State.running
Game->>Input: handleEvents(State)
alt accumulator >= FIXED_TIMESTEP
Game->>Game: step(FIXED_TIMESTEP)
Game->>State: ++gameTicks
end
Game->>Renderer: render(State, deltaSeconds)
Renderer->>GD: beginFrame()
Renderer->>GD: drawTexture(... world tiles ...)
Renderer->>GD: drawTexture(... player ...)
opt paused/debug
Renderer->>GD: drawOverlay()/drawText()
end
GD-->>Renderer: present frame
end
Main-->>User: exit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
@coderabbitai give feedback on current separation and possible improvements in the direction of abstracting platform and windowing away from game logic and hidden behind an api. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainI'll analyze the current platform abstraction in your codebase to provide feedback on the separation and suggest improvements. 🏁 Script executed: Length of output: 1138 🏁 Script executed: Length of output: 4930 🏁 Script executed: Length of output: 2727 🏁 Script executed: Length of output: 1310 🏁 Script executed: Length of output: 437 🏁 Script executed: Length of output: 2633 Let me examine the key files to understand the current abstraction implementation: 🏁 Script executed: Length of output: 2308 🏁 Script executed: Length of output: 3524 🏁 Script executed: Length of output: 1568 🏁 Script executed: Length of output: 1234 @jacopouggeri I've analyzed your platform abstraction implementation. Here's my comprehensive feedback on the current separation and suggestions for improvement: Current Architecture Assessment✅ Strengths in Current Design
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
src/game/resources/TextureManager.cpp (3)
5-11: Missing standard headers for used features.This TU uses
std::vector(or should be switched tostd::array),std::ranges::views::values, andstd::format-like formatting (which should be replaced per below). At minimum, add the missing includes or refactor away fromstd::format.Option A — add missing headers (if you keep vector):
#include "game/resources/TextureManager.h" #include "game/Config.h" #include <SDL.h> #include <SDL_image.h> #include <filesystem> #include <iostream> #include <ranges> +#include <vector>Option B — switch
essentialstostd::array(see separate comment) and avoidstd::format(see below), then add:+#include <array>
29-41: Make path construction explicit and include SDL/SDL_image error details; avoid std::format here.
(TEX_PATH / textureName)relies on an implicitstd::filesystem::pathconversion fromstd::string_viewwhich is not guaranteed on all standard libraries. Construct thepathexplicitly.- Logging should include
IMG_GetError()andSDL_GetError()to aid debugging.- Replacing
std::formatimproves portability and avoids an extra dependency here.-void TextureManager::loadTexture(std::string_view textureName, SDL_Renderer* renderer) { - SDL_Surface* surf = IMG_Load((TEX_PATH / textureName).c_str()); - if (!surf) { - std::cerr << std::format("Failed to load texture with name: {}\n", std::string(textureName)); - return; - } - - SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); - SDL_FreeSurface(surf); - if (!tex) { - std::cerr << std::format("Failed to create texture with name: {}", std::string(textureName)); - return; - } +void TextureManager::loadTexture(std::string_view textureName, SDL_Renderer* renderer) { + const std::filesystem::path path = TEX_PATH / std::filesystem::path{textureName}; + SDL_Surface* surf = IMG_Load(path.string().c_str()); + if (!surf) { + std::cerr << "IMG_Load failed for " << path << ": " << IMG_GetError() << '\n'; + return; + } + + SDL_Texture* tex = SDL_CreateTextureFromSurface(renderer, surf); + SDL_FreeSurface(surf); + if (!tex) { + std::cerr << "SDL_CreateTextureFromSurface failed for " << path << ": " << SDL_GetError() << '\n'; + return; + }
43-45: Prevent leak on duplicate texture names.If
textureNamealready exists,emplacefails andtexleaks. Usetry_emplaceand destroy on duplicate.- // Cache loaded textures - textures.emplace(textureName, tex); + // Cache loaded textures + auto [it, inserted] = textures.try_emplace(std::string(textureName), tex); + if (!inserted) { + SDL_DestroyTexture(tex); // avoid leak on duplicate load + }include/game/Player.h (1)
14-17: Broken initializer: PLAYER_START_POS references removed WORLD_SIZE; Sprite/PLAYER_TEX deps unresolved
- WORLD_SIZE is no longer declared in this header but is still used in the PLAYER_START_POS initializer, causing a compile error.
- Sprite is used but this header includes TextureManager instead of the Sprite type; PLAYER_TEX is referenced without including the header that defines it.
Apply one of the following fixes.
Option A (self-contained Player default):
-#include "resources/TextureManager.h" +#include <string> +#include "resources/Sprite.h" // defines Sprite +#include "game/GameConstants.h" // defines PLAYER_TEX, WORLD_*_CHUNKS, CHUNK_SIZE (adjust include if moved) struct Player { constexpr static float SPEED = 10.0f; constexpr static float JUMP_SPEED = 10.0f; + // Keep Player self-contained by restoring compile-time world size here + constexpr static phys::Vec2i WORLD_SIZE {WORLD_WIDTH_CHUNKS * CHUNK_SIZE, + WORLD_HEIGHT_CHUNKS * CHUNK_SIZE}; constexpr static phys::Vec2f PLAYER_START_POS {static_cast<phys::Vec2f>(WORLD_SIZE) / 2.0f}; phys::Vec2f pos = PLAYER_START_POS; phys::Vec2f vel; Sprite sprite {std::string(PLAYER_TEX)};Option B (defer start position to runtime after world is initialized):
-#include "resources/TextureManager.h" +#include <string> +#include "resources/Sprite.h" // defines Sprite +#include "game/GameConstants.h" // defines PLAYER_TEX struct Player { constexpr static float SPEED = 10.0f; constexpr static float JUMP_SPEED = 10.0f; - constexpr static phys::Vec2f PLAYER_START_POS {static_cast<phys::Vec2f>(WORLD_SIZE) / 2.0f}; - phys::Vec2f pos = PLAYER_START_POS; + phys::Vec2f pos {}; // set from Game::init() after world.init() phys::Vec2f vel; Sprite sprite {std::string(PLAYER_TEX)};If you choose Option B, also set the start position in Game::init() (see my comment on src/game/Game.cpp for a suggested snippet).
🧹 Nitpick comments (32)
.clang-format (2)
1-3: Add a trailing newline at EOF to avoid tooling diffs.Many linters and VCS UIs flag files without a terminal newline. Please add one.
1-3: Consider pinning a few more style knobs to stabilize formatting across platforms.Optional, but commonly set with LLVM base:
- ColumnLimit (e.g., 120)
- AlignConsecutiveAssignments/Declarations
- SortIncludes, IncludeBlocks, IncludeIsMainRegex
- DerivePointerAlignment = false, PointerAlignment = Left
Helps avoid churn as the codebase grows.
include/game/Config.h (3)
5-7: Choose either header guards or#pragma once, not both.Redundant but harmless. Pick one to reduce boilerplate (I’d keep
#pragma once).-#ifndef CONFIG_H -#define CONFIG_H -#pragma once +#pragma onceIf you prefer guards, then drop
#pragma onceand keep the guard (remember to keep the#endif // CONFIG_H).
23-23: Use a float literal to avoid implicit narrowing.
DRAW_SCALEisfloatbut initialized with adoubleliteral. Minor, but easy to make explicit.-inline constexpr float DRAW_SCALE = 4.0; +inline constexpr float DRAW_SCALE = 4.0f;
13-24: Group config into a namespace to avoid global symbol bleed.These inline globals are fine but will pollute the global namespace as the project grows. Consider
namespace game::config { ... }(or astruct Configwithinline staticmembers).CMakeLists.txt (1)
41-57: ASan/UBSan: add frame pointers for better diagnostics in Debug.Small tweak that improves ASan stack traces on GCC/Clang.
target_compile_options(${PROJECT_NAME} PRIVATE $<$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>: - -Wall;-Wextra;-Wpedantic + -Wall;-Wextra;-Wpedantic > $<$<AND:$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>,$<CONFIG:Debug>>: - -fsanitize=address,undefined + -fsanitize=address,undefined;-fno-omit-frame-pointer >src/platform/main.cpp (1)
1-2: Prefer installed include paths over relative paths; include cstdlib for EXIT_ macros.*Since
include/is on the target’s include path, use#include "game/Game.h"instead of a relative../../path. Also include<cstdlib>forEXIT_SUCCESS/EXIT_FAILURE.-#include "../../include/game/Game.h" -#include <SDL.h> +#include "game/Game.h" +#include <SDL.h> +#include <cstdlib>Note: keeping
<SDL.h>here is fine if you linkSDL2::SDL2mainas suggested in CMake.src/game/resources/TextureManager.cpp (3)
5-7: Avoid brittle relative includes; include from the public include root.Using a relative path to reach the
include/tree is fragile and breaks when build directory layout changes. Prefer configured include paths with project-style includes.-#include "../../../include/game/resources/TextureManager.h" -#include "../../../include/game/Config.h" +#include "game/resources/TextureManager.h" +#include "game/Config.h"
19-27: Prefer constexpr std::array over CTAD vector for string literals.CTAD with string literals deduces
std::vector<const char*>, which is fine withstd::string_viewbut unnecessarily heap-allocates. A fixedstd::array<std::string_view, N>is cheaper and clearer. Also consider reserving buckets up front.-void TextureManager::loadTextures(SDL_Renderer* renderer) -{ - const std::vector essentials = { - PLAYER_TEX, WALL_TEX - }; - for (const auto& tex : essentials) { - loadTexture(tex, renderer); - } -} +void TextureManager::loadTextures(SDL_Renderer* renderer) +{ + constexpr std::array<std::string_view, 2> essentials{ PLAYER_TEX, WALL_TEX }; + // reduce rehashing when loading essentials + textures.reserve(textures.size() + essentials.size()); + for (const auto& name : essentials) { + loadTexture(name, renderer); + } +}
13-17: Match destructor declaration if you add noexcept to header.If you adopt the
noexceptdestructor signature in the header (see header review), mirror it here.-TextureManager::~TextureManager() { +TextureManager::~TextureManager() noexcept { for (const auto& tex : textures | std::views::values) { SDL_DestroyTexture(tex); } }include/game/resources/TextureManager.h (3)
9-11: Decouple from SDL headers in public interface; forward-declare SDL types.Including
<SDL.h>in a widely included header increases compile times and coupling. Forward-declareSDL_RendererandSDL_Texturehere and include<SDL.h>only in the .cpp.-#include "game/Config.h" - -#include <SDL.h> +#include "game/Config.h" + +struct SDL_Renderer; +struct SDL_Texture;
25-25: Destructor can be noexcept.It only releases resources via SDL and does not throw. Marking it
noexceptdocuments intent and can help optimizers.- ~TextureManager(); + ~TextureManager() noexcept;
16-20: Consider moving Sprite to its own header.Defining
struct Spriteinside TextureManager’s header conflates concerns and propagates a resources include into rendering and gameplay code. A standaloneSprite.hwould improve modularity.I can prep a minimal
Sprite.hand update includes if you want.include/platform/InputSource.h (1)
12-14: Prefer references over pointers for SDL_Event parameter.Passing
SDL_Event&communicates non-null more clearly and matches modern style. This is purely API ergonomics; keep as-is if changing call sites is noisy right now.-void handleEvents(GameState& state, SDL_Event* e); +void handleEvents(GameState& state, SDL_Event& e);include/game/Player.h (2)
7-9: Tighten dependencies: avoid including TextureManager in a domain model headerPlayer should not depend on TextureManager; include the Sprite type directly and the constants header that defines PLAYER_TEX. This reduces layering leaks and rebuild churn.
Apply this include-only change if you keep Option A’s WORLD_SIZE inside Player:
-#include "resources/TextureManager.h" +#include <string> +#include "resources/Sprite.h" +#include "game/GameConstants.h" // adjust path if constants moved
17-17: Missing include for std::string in default member initializerThe Sprite initializer constructs a std::string; include in this header to keep it self-sufficient.
See include diff above adding:
+#include <string>src/platform/Graphics.cpp (2)
58-70: Close the lazily created TTF_Font to avoid a leakdrawText() keeps a static font open for the program’s lifetime. Ensure it’s closed in destroy(). Easiest is to hoist the static to file scope and close it here.
Apply:
+#include <format> +#include <cmath> +static TTF_Font* g_font = nullptr; @@ void Graphics::destroy() noexcept { if (renderer_) { SDL_DestroyRenderer(renderer_); renderer_ = nullptr; } if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; } + if (g_font) { + TTF_CloseFont(g_font); + g_font = nullptr; + } TTF_Quit(); IMG_Quit(); SDL_Quit(); }And in drawText() (see next comment) use g_font instead of a function-local static font.
144-151: Use a shared font handle instead of a function-local static and guard resource creationThis avoids multiple states and allows proper cleanup in destroy().
Apply:
- static TTF_Font* font = nullptr; - if (!font) { - font = TTF_OpenFont("assets/fonts/Hack-Regular.ttf", 16); - if (!font) { + if (!g_font) { + g_font = TTF_OpenFont("assets/fonts/Hack-Regular.ttf", 16); + if (!g_font) { std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; } } @@ - if (SDL_Surface *textSurface = TTF_RenderText_Blended( - font, std::string(text).c_str(), color)) { + if (SDL_Surface *textSurface = TTF_RenderText_Blended( + g_font, std::string(text).c_str(), color)) {include/game/Game.h (1)
14-15: Mark init() [[nodiscard]] to enforce call-site checkingPrevents accidental ignoring of initialization failure in callers.
- bool init(); + [[nodiscard]] bool init();src/platform/InputSource.cpp (1)
24-39: Potential coordinate-space mismatch: mouse uses screen pixels, Player expects world coordsonMouseButtonDown/onMouseMotion assign e->x/e->y (screen space in pixels) directly to player.pos, while rendering uses toScreenCoords(world) and TILE_SIZE. This likely places the player at the wrong world position.
- Confirm whether Player::pos is in world tiles or pixels.
- If tiles, convert screen->world before assignment. Two paths:
- Add Graphics::fromScreenCoords(...) and call it here (requires passing Graphics to input handlers), or
- Store camera position in GameState and compute world coords here.
I can sketch either approach; Option 1 keeps camera concerns encapsulated in Graphics and avoids duplicating math across modules.
Example API (conceptual):// in Graphics.h/.cpp phys::Vec2f fromScreenCoords(const phys::Vec2f& s) const { int w,h; SDL_GetRendererOutputSize(renderer_, &w, &h); const phys::Vec2f ws = static_cast<phys::Vec2f>(phys::Vec2i{w,h}); return (s - ws/2.0f) / TILE_SIZE + camera_.pos; }Then, InputSource would set:
gameState.player.pos = graphics.fromScreenCoords({float(mx), float(my)});src/game/world/World.cpp (6)
5-5: Normalize include path to be project-root relative (avoid fragile ../../../).Relying on deep relative paths is brittle and makes moving files harder. Prefer a project include root (e.g., -Iinclude) and include by module path.
-#include "../../../include/game/world/World.h" +#include "game/world/World.h"
6-7: Include explicitly for std::out_of_range.Some standard library headers may drag it in incidentally, but it’s not guaranteed. Include it directly for clarity and portability.
#include <iostream> +#include <stdexcept>
8-14: Use size_t for the 1D index into std::array and avoid signed-to-unsigned pitfalls.Bounds are already checked, but computing the index as size_t avoids signed/unsigned conversions and potential sanitizer noise.
Tile& Chunk::getTile(int tileX, int tileY) { if (tileX < 0 || tileX >= CHUNK_SIZE || tileY < 0 || tileY >= CHUNK_SIZE) { std::cerr << "Invalid tile access: (" << tileX << ", " << tileY << ")" << std::endl; throw std::out_of_range("Invalid tile access"); } - return tiles[tileY*CHUNK_SIZE + tileX]; + const std::size_t idx = + static_cast<std::size_t>(tileY) * static_cast<std::size_t>(CHUNK_SIZE) + + static_cast<std::size_t>(tileX); + return tiles[idx]; }
16-28: Avoid unnecessary std::string construction for textureName assignment.Assuming Sprite::textureName is a std::string, assigning the const char* is fine and avoids an extra explicit construction.
- tile.sprite.textureName = std::string(WALL_TEX); + tile.sprite.textureName = WALL_TEX;
31-37: Index world chunks with size_t for the 1D mapping.Type-align the index expression with std::array::operator[] to prevent implicit signed-to-unsigned conversions.
- return chunks[chunkY*WORLD_WIDTH_CHUNKS + chunkX]; + const std::size_t idx = + static_cast<std::size_t>(chunkY) * static_cast<std::size_t>(WORLD_WIDTH_CHUNKS) + + static_cast<std::size_t>(chunkX); + return chunks[idx];
8-14: Avoid double-reporting errors: prefer exception message over stderr in library code.Printing to std::cerr and then throwing can spam logs in higher-level loops. Consider removing the cerr in favor of richer exception messages (or gate it behind a debug macro).
Also applies to: 31-37
include/game/world/World.h (6)
5-7: Redundant include guard and pragma once. Pick one.Both are fine, but redundant. Retain the guard and drop
#pragma once(or vice versa) for consistency.#ifndef SANDBOX2D_WORLD_H #define SANDBOX2D_WORLD_H -#pragma once
9-11: Consider standardizing include paths to module-style (game/...) instead of relative.Using project-root-relative includes tends to be more portable and consistent across modules when the build system adds
include/to include dirs.
13-20: Constexpr globals look good; minor style tweak optional.
constexpr static phys::Vec2i WORLD_SIZEis valid; as a small style tweak, you could dropstatic(constexpr implies internal linkage here) or make itinline constexprif you ever want external linkage.Optional tweak:
-constexpr static phys::Vec2i WORLD_SIZE {WORLD_WIDTH_CHUNKS * CHUNK_SIZE, WORLD_HEIGHT_CHUNKS * CHUNK_SIZE}; +constexpr phys::Vec2i WORLD_SIZE { WORLD_WIDTH_CHUNKS * CHUNK_SIZE, WORLD_HEIGHT_CHUNKS * CHUNK_SIZE };
25-29: Add a const-qualified overload for Chunk::getTile for read-only access paths.This improves API usability and avoids const_casts at call sites.
struct Chunk { std::array<Tile, CHUNK_SIZE*CHUNK_SIZE> tiles; Tile& getTile(int tileX, int tileY); + const Tile& getTile(int tileX, int tileY) const; };Implementation to add in World.cpp:
const Tile& Chunk::getTile(int tileX, int tileY) const { if (tileX < 0 || tileX >= CHUNK_SIZE || tileY < 0 || tileY >= CHUNK_SIZE) { std::cerr << "Invalid tile access: (" << tileX << ", " << tileY << ")\n"; throw std::out_of_range("Invalid tile access"); } const std::size_t idx = static_cast<std::size_t>(tileY) * static_cast<std::size_t>(CHUNK_SIZE) + static_cast<std::size_t>(tileX); return tiles[idx]; }
31-39: Add const-qualified overloads for World accessors.These are frequently useful in rendering and query code paths that shouldn’t mutate world state.
struct World { std::array<Chunk, CHUNK_COUNT> chunks; void init(); Tile& getTileGlobal(int worldX, int worldY); + const Tile& getTileGlobal(int worldX, int worldY) const; Chunk& getChunk(int chunkX, int chunkY); + const Chunk& getChunk(int chunkX, int chunkY) const;Implementations to add in World.cpp:
const Chunk& World::getChunk(int chunkX, int chunkY) const { if (chunkX < 0 || chunkX >= WORLD_WIDTH_CHUNKS || chunkY < 0 || chunkY >= WORLD_HEIGHT_CHUNKS) { std::cerr << "Invalid chunk access: (" << chunkX << ", " << chunkY << ")\n"; throw std::out_of_range("Invalid chunk access"); } const std::size_t idx = static_cast<std::size_t>(chunkY) * static_cast<std::size_t>(WORLD_WIDTH_CHUNKS) + static_cast<std::size_t>(chunkX); return chunks[idx]; } const Tile& World::getTileGlobal(int worldX, int worldY) const { const int chunkX = worldX / CHUNK_SIZE; const int chunkY = worldY / CHUNK_SIZE; const int tileX = worldX % CHUNK_SIZE; const int tileY = worldY % CHUNK_SIZE; return getChunk(chunkX, chunkY).getTile(tileX, tileY); }
13-20: Add quick tests for boundaries and mapping (optional but valuable with refactor).Given the refactor, a few light unit tests would lock in behavior:
- getTile bounds: (-1,0), (0,-1), (CHUNK_SIZE,0), (0,CHUNK_SIZE) throw.
- getChunk bounds: same pattern with WORLD_*_CHUNKS.
- getTileGlobal mapping at chunk edges: (0,0), (CHUNK_SIZE-1, CHUNK_SIZE-1), (CHUNK_SIZE,0).
- If negatives are allowed: mapping correctness with floor-division semantics.
I can scaffold minimal tests using doctest or your existing test framework if you want.
Also applies to: 31-39
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
.clang-format(1 hunks)CMakeLists.txt(1 hunks)include/game/Config.h(2 hunks)include/game/Game.h(1 hunks)include/game/GameState.h(1 hunks)include/game/Player.h(2 hunks)include/game/resources/TextureManager.h(2 hunks)include/game/world/World.h(1 hunks)include/platform/Graphics.h(1 hunks)include/platform/InputSource.h(1 hunks)src/core/world/World.cpp(0 hunks)src/core/world/World.h(0 hunks)src/game/Game.cpp(1 hunks)src/game/Player.cpp(2 hunks)src/game/resources/TextureManager.cpp(1 hunks)src/game/world/World.cpp(1 hunks)src/main.cpp(0 hunks)src/platform/Graphics.cpp(4 hunks)src/platform/InputSource.cpp(3 hunks)src/platform/main.cpp(1 hunks)
💤 Files with no reviewable changes (3)
- src/core/world/World.cpp
- src/main.cpp
- src/core/world/World.h
🧰 Additional context used
🧬 Code Graph Analysis (15)
include/game/Config.h (1)
src/main.cpp (1)
main(39-50)
src/game/resources/TextureManager.cpp (2)
src/core/resources/TextureManager.h (2)
TextureManager(22-38)struct Sprite {(16-18)src/core/resources/TextureManager.cpp (1)
TextureManager(13-17)
include/game/GameState.h (2)
src/core/GameState.h (3)
deltaSeconds(28-35)WORLD_SIZE(15-19)sprite(22-26)src/main.cpp (2)
main(39-50)loop(8-37)
src/platform/main.cpp (2)
src/main.cpp (2)
main(39-50)loop(8-37)src/core/Graphics.cpp (1)
init(16-58)
include/game/Game.h (6)
src/game/Game.cpp (6)
init(8-14)init(8-8)loop(20-49)loop(20-20)step(16-18)step(16-16)src/platform/Graphics.cpp (2)
init(14-56)init(14-14)src/main.cpp (2)
main(39-50)loop(8-37)src/core/GameState.h (2)
deltaSeconds(28-35)sprite(22-26)src/core/Graphics.h (1)
vsyncEnabled_(21-58)src/core/GameState.cpp (1)
step(16-18)
include/game/resources/TextureManager.h (2)
src/core/GameState.h (1)
sprite(22-26)src/core/Graphics.h (1)
vsyncEnabled_(21-58)
include/platform/InputSource.h (4)
src/platform/InputSource.cpp (4)
handleEvents(41-59)handleEvents(41-41)handlePlayerInput(61-77)handlePlayerInput(61-61)src/core/ioevents.h (5)
handlePlayerInput(63-79)handleEvents(43-61)onKeyDown(13-25)onMouseMotion(35-41)onMouseButtonDown(27-33)src/main.cpp (1)
loop(8-37)src/core/GameState.h (1)
deltaSeconds(28-35)
src/game/world/World.cpp (4)
src/game/Game.cpp (2)
init(8-14)init(8-8)include/game/world/World.h (1)
chunks(31-46)src/core/world/World.h (7)
chunks(37-50)chunks(37-61)tiles(25-32)nodiscard(71-73)nodiscard(79-84)nodiscard(75-77)struct Tile {(21-23)src/core/Graphics.cpp (1)
drawWorld(105-117)
include/game/Player.h (3)
src/core/GameState.h (3)
deltaSeconds(28-35)WORLD_SIZE(15-19)sprite(22-26)src/core/GameState.cpp (2)
step(16-18)move(11-14)src/main.cpp (2)
main(39-50)loop(8-37)
src/platform/Graphics.cpp (3)
include/platform/Graphics.h (1)
destroy(34-42)src/main.cpp (1)
main(39-50)src/core/resources/TextureManager.h (1)
TextureManager(22-38)
src/game/Game.cpp (5)
src/platform/Graphics.cpp (2)
init(14-56)init(14-14)src/platform/InputSource.cpp (4)
handleEvents(41-59)handleEvents(41-41)handlePlayerInput(61-77)handlePlayerInput(61-61)src/main.cpp (2)
loop(8-37)main(39-50)src/core/GameState.h (1)
deltaSeconds(28-35)src/core/GameState.cpp (2)
step(16-18)move(11-14)
src/game/Player.cpp (5)
src/core/GameState.cpp (3)
step(16-18)move(11-14)set_velocity(7-9)src/core/GameState.h (3)
sprite(22-26)deltaSeconds(28-35)WORLD_SIZE(15-19)src/main.cpp (2)
loop(8-37)main(39-50)src/core/Graphics.cpp (1)
cameraFollow(60-67)src/core/ioevents.h (1)
handlePlayerInput(63-79)
include/game/world/World.h (3)
include/game/physics/Vec2.h (2)
nodiscard(24-28)phys(11-22)src/core/world/World.h (6)
chunks(37-50)chunks(37-61)tiles(25-32)nodiscard(79-84)nodiscard(71-73)nodiscard(75-77)src/core/Graphics.cpp (1)
drawWorld(105-117)
src/platform/InputSource.cpp (1)
src/main.cpp (1)
loop(8-37)
include/platform/Graphics.h (2)
src/game/Game.cpp (2)
init(8-14)init(8-8)src/platform/Graphics.cpp (16)
init(14-56)init(14-14)draw(156-182)draw(156-156)cameraFollow(72-79)cameraFollow(72-72)destroy(58-70)destroy(58-58)toScreenCoords(93-98)toScreenCoords(93-93)drawSprite(100-114)drawSprite(100-100)drawWorld(116-128)drawWorld(116-116)drawText(130-154)drawText(130-130)
🔇 Additional comments (13)
CMakeLists.txt (1)
9-11: Confirm recursive globbing is intentional.
GLOB_RECURSEonsrc/*.cppwill also capture platform- or tool-specific entry points if added later (e.g., multiple mains). Fine for now, but be aware this can silently pull in unexpected files. Consider explicit lists once layout stabilizes.include/platform/Graphics.h (1)
40-49: Good call on deleting copy/move; RAII semantics are clearer.The explicit deletion of copy/move avoids accidental renderer/window ownership transfer. With
destroy()wired in the destructor, the lifetime is well-scoped.src/platform/main.cpp (1)
4-9: Main flow looks clean and minimal.Entrypoint delegates to
Gamecleanly; return codes are correct.src/game/resources/TextureManager.cpp (1)
29-41: SDL_image initialization verified
The call toIMG_Init(IMG_INIT_PNG)insrc/platform/Graphics.cpp:20occurs beforetextureManager_.loadTextures(renderer_)at line 54, so SDL_image is correctly initialized prior to any texture loading. No further changes needed.include/game/GameState.h (1)
10-15: No remainingGameState::stepcalls detectedI ran a repository-wide search for the removed method using patterns for both member-access and pointer-access (
gameState.step(,gameState->step(, andGameState::step) and confirmed there are zero occurrences. All call sites have been updated to the new player/world update methods.src/game/Player.cpp (1)
12-15: LGTM on movement integration.Velocity integration is straightforward and consistent with
Vec2semantics. No issues found here.include/game/Player.h (1)
14-17: Ensure correct includes for Sprite and PLAYER_TEX
- Sprite is defined in
include/game/resources/TextureManager.h, soPlayer.hneeds:#include "game/resources/TextureManager.h"- No occurrences of
PLAYER_TEXwere found in the repo scan—please locate the header (or add the definition) that declaresPLAYER_TEXand include it inPlayer.h.src/platform/Graphics.cpp (1)
103-108: LGTM: explicit float casts for destRect sizingSwitching to static_cast improves clarity and avoids C-style casts.
src/game/Game.cpp (2)
16-18: LGTM: step delegates to Player::moveSeparation keeps GameState logic simple and aligns with the new architecture.
8-14: No manual player positioning needed in Game::init()The
PLAYER_START_POSconstant is still defined inPlayer.h(include/game/Player.h:14), so the player’s start position is already initialized via that static member. You can safely leaveGame::init()as-is.If you later remove
PLAYER_START_POSfromPlayer.h(i.e. apply “Option B”), then uncomment or add the following afterworld.init()to center the player:#include "game/world/World.h" // … bool Game::init() { if (!graphics_.init(WINDOW_SIZE.x, WINDOW_SIZE.y, GAME_NAME)) { return false; } gameState_.world.init(); // If PLAYER_START_POS was removed, center player in world: // gameState_.player.pos = { // WORLD_SIZE.x / 2.0f, // WORLD_SIZE.y / 2.0f // }; return true; }src/platform/InputSource.cpp (1)
41-59: LGTM: Event dispatch and normalized WASD input are cleanPolling loop and diagonal normalization are straightforward and easy to extend.
Also applies to: 61-77
src/game/world/World.cpp (1)
39-44: Please verify negative-coordinate handling ingetTileGlobal.Currently,
World::getTileGlobal(int worldX, int worldY)uses C++’s truncating division and remainder:const int chunkX = worldX / CHUNK_SIZE; const int chunkY = worldY / CHUNK_SIZE; const int tileX = worldX % CHUNK_SIZE; const int tileY = worldY % CHUNK_SIZE; return getChunk(chunkX, chunkY).getTile(tileX, tileY);– for negative
worldX/worldY, this produces a non-floor quotient and a negative remainder (e.g.-1/16 == 0,-1%16 == -1), likely triggering out-of-bounds ingetTilerather than mapping into[0, CHUNK_SIZE).• If your design disallows negative world coordinates, please confirm callers never pass negatives, or enforce a precondition/assertion.
• If you intend to support negative coordinates (e.g. origin at center), consider switching to floor-division semantics. For example:Tile& World::getTileGlobal(int worldX, int worldY) { - const int chunkX = worldX / CHUNK_SIZE; - const int chunkY = worldY / CHUNK_SIZE; - const int tileX = worldX % CHUNK_SIZE; - const int tileY = worldY % CHUNK_SIZE; + // floor-divide a by b: q = ⌊a/b⌋, r = a − b·q ∈ [0, b) + auto divMod = [](int a, int b) { + int q = a / b; + int r = a % b; + if (r < 0) { r += b; --q; } + return std::pair{q, r}; + }; + const auto [chunkX, tileX] = divMod(worldX, CHUNK_SIZE); + const auto [chunkY, tileY] = divMod(worldY, CHUNK_SIZE); return getChunk(chunkX, chunkY).getTile(tileX, tileY); }You’ll need
#include <utility>forstd::pair.• Action: Please confirm which coordinate conventions you intend for
getTileGlobal. If negatives are valid, apply the above refactor (or similar).
• Location:src/game/world/World.cpplines 39–45.include/game/world/World.h (1)
40-53: Coordinate helpers are clean and constexpr; good use of [[nodiscard]].The 2D↔1D mapping is correct, and constexpr enables compile-time folding. Nicely done.
| target_link_libraries(${PROJECT_NAME} | ||
| PRIVATE | ||
| SDL2::SDL2 | ||
| SDL2_image::SDL2_image | ||
| SDL2_ttf::SDL2_ttf | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Windows entry-point linkage: add SDL2::SDL2main or define SDL_MAIN_HANDLED.
With #include <SDL.h> in main.cpp, the main symbol is usually remapped to SDL_main and requires linking SDL2::SDL2main on Windows. Without it, MSVC builds often fail at link time. Two options:
- Link
SDL2::SDL2main(recommended when you include<SDL.h>in your main TU):
target_link_libraries(${PROJECT_NAME}
PRIVATE
- SDL2::SDL2
+ SDL2::SDL2
+ SDL2::SDL2main
SDL2_image::SDL2_image
SDL2_ttf::SDL2_ttf
)- Or define
SDL_MAIN_HANDLEDand include<SDL.h>for APIs only (then you typically don’t linkSDL2main). If you go this route, add:
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_HANDLED)and call SDL_SetMainReady() before SDL_Init() (desktop often works without it, but it’s the documented sequence).
🏁 Script executed:
#!/usr/bin/env bash
# Verify Windows entrypoint setup in the repo.
set -euo pipefail
echo "Searching for SDL2::SDL2main in CMakeLists.txt:"
rg -nP '\bSDL2::SDL2main\b' CMakeLists.txt || true
echo
echo "Looking for SDL_MAIN_HANDLED / SDL_SetMainReady in sources:"
rg -nP 'SDL_MAIN_HANDLED|SDL_SetMainReady' -g '!**/build/**' || trueLength of output: 383
Link Windows entry-point library or define SDL_MAIN_HANDLED
On Windows, SDL remaps main() to SDL_main, which requires either linking against SDL2::SDL2main or defining SDL_MAIN_HANDLED and calling SDL_SetMainReady() before SDL_Init(). Our verification shows you’re currently linking only SDL2::SDL2, SDL2_image, and SDL2_ttf, and there’s no evidence of SDL2::SDL2main or the SDL_MAIN_HANDLED macro in the codebase.
Please address one of the following in your CMakeLists.txt and source:
• Add the SDL2main library so that the default remapping works:
target_link_libraries(${PROJECT_NAME}
PRIVATE
- SDL2::SDL2
+ SDL2::SDL2main
+ SDL2::SDL2
SDL2_image::SDL2_image
SDL2_ttf::SDL2_ttf
)• Or keep your custom main() signature by defining SDL_MAIN_HANDLED and initializing correctly:
- In
CMakeLists.txt:target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_HANDLED )
- In your
main.cpp, before any SDL calls:#include <SDL.h> SDL_SetMainReady(); // then SDL_Init(…);
Adjusting one of these options will resolve Windows link errors related to the SDL entry-point.
🤖 Prompt for AI Agents
CMakeLists.txt lines ~23-28: Windows needs SDL entry-point handling; either link
SDL2main or define SDL_MAIN_HANDLED and call SDL_SetMainReady() before SDL_Init.
Fix option A: add SDL2::SDL2main to target_link_libraries alongside
SDL2::SDL2/SDL2_image/SDL2_ttf. Fix option B: add
target_compile_definitions(${PROJECT_NAME} PRIVATE SDL_MAIN_HANDLED) in
CMakeLists.txt and in main.cpp include <SDL.h> and call SDL_SetMainReady()
before any SDL calls (e.g., before SDL_Init).
include/platform/Graphics.h
Outdated
| SDL_Window* window_ {nullptr}; | ||
| SDL_Renderer* renderer_ {nullptr}; | ||
| TextureManager textureManager_ {}; | ||
| double framesPerSecond_ {}; | ||
| bool vsyncEnabled_ {true}; | ||
| class Camera { public: phys::Vec2f pos {}; } camera_; | ||
|
|
||
| void destroy() noexcept; | ||
| [[nodiscard]] phys::Vec2f toScreenCoords(const phys::Vec2f& v) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Resource teardown order: ensure textures are freed before renderer/window.
If TextureManager explicitly destroys textures in its destructor, calling destroy() first (which destroys the renderer) and then letting members destruct can risk double-destroys or invalid frees depending on SDL’s semantics and your manager’s behavior. Prefer explicit texture cleanup before destroying the renderer.
If TextureManager exposes a clear() method, consider:
void destroy() noexcept;And in Graphics.cpp:
void Graphics::destroy() noexcept {
// Ensure textures released first
textureManager_.clear(); // no-op if already empty
if (renderer_) { SDL_DestroyRenderer(renderer_); renderer_ = nullptr; }
if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; }
// Close fonts (see previous comment) before quitting TTF.
if (fpsFont_) { TTF_CloseFont(fpsFont_); fpsFont_ = nullptr; }
TTF_Quit();
IMG_Quit();
SDL_Quit();
}To verify, please check whether TextureManager’s destructor or clear() calls SDL_DestroyTexture:
🏁 Script executed:
#!/usr/bin/env bash
rg -nP 'class\s+TextureManager|~\s*TextureManager|SDL_DestroyTexture|clear\s*\(' -C2 include srcLength of output: 1479
Ensure TextureManager cleans up textures before tearing down the SDL renderer
TextureManager’s destructor iterates over all stored textures and calls SDL_DestroyTexture (src/game/resources/TextureManager.cpp:13–17). If Graphics::destroy() destroys the renderer first, those SDL_DestroyTexture calls will operate on an invalid renderer, leading to undefined behavior. To prevent this:
• Add a clear() method to TextureManager that destroys every texture and clears the container.
• Call textureManager_.clear() at the start of Graphics::destroy() so all textures are freed before you destroy the renderer/window.
Suggested diff:
--- include/game/resources/TextureManager.h
+++ include/game/resources/TextureManager.h
@@ class TextureManager {
- ~TextureManager();
+ ~TextureManager();
+ // Explicitly free all textures on demand
+ void clear() noexcept;--- src/game/resources/TextureManager.cpp
+++ src/game/resources/TextureManager.cpp
TextureManager::~TextureManager() {
for (const auto& tex : textures | std::views::values) {
SDL_DestroyTexture(tex);
}
}
+
+void TextureManager::clear() noexcept {
+ for (const auto& tex : textures | std::views::values) {
+ SDL_DestroyTexture(tex);
+ }
+ textures.clear();
+}--- src/platform/Graphics.cpp
+++ src/platform/Graphics.cpp
void Graphics::destroy() noexcept {
- if (renderer_) { SDL_DestroyRenderer(renderer_); renderer_ = nullptr; }
- if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; }
+ // Release all textures before tearing down the renderer
+ textureManager_.clear();
+
+ if (renderer_) { SDL_DestroyRenderer(renderer_); renderer_ = nullptr; }
+ if (window_) { SDL_DestroyWindow(window_); window_ = nullptr; }
// Close fonts before quitting TTF
if (fpsFont_) { TTF_CloseFont(fpsFont_); fpsFont_ = nullptr; }
TTF_Quit();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| SDL_Window* window_ {nullptr}; | |
| SDL_Renderer* renderer_ {nullptr}; | |
| TextureManager textureManager_ {}; | |
| double framesPerSecond_ {}; | |
| bool vsyncEnabled_ {true}; | |
| class Camera { public: phys::Vec2f pos {}; } camera_; | |
| void destroy() noexcept; | |
| [[nodiscard]] phys::Vec2f toScreenCoords(const phys::Vec2f& v) const; | |
| // File: include/game/resources/TextureManager.h | |
| #pragma once | |
| #include <unordered_map> | |
| #include <SDL.h> | |
| #include <string> | |
| #include <ranges> | |
| class TextureManager { | |
| public: | |
| // Destroy all remaining textures on program exit | |
| ~TextureManager(); | |
| // Explicitly free all textures on demand | |
| void clear() noexcept; | |
| // Loads and stores textures keyed by name | |
| SDL_Texture* load(const std::string& key, SDL_Renderer* renderer, const std::string& path); | |
| SDL_Texture* get(const std::string& key) const; | |
| private: | |
| std::unordered_map<std::string, SDL_Texture*> textures; | |
| }; |
| SDL_Window* window_ {nullptr}; | |
| SDL_Renderer* renderer_ {nullptr}; | |
| TextureManager textureManager_ {}; | |
| double framesPerSecond_ {}; | |
| bool vsyncEnabled_ {true}; | |
| class Camera { public: phys::Vec2f pos {}; } camera_; | |
| void destroy() noexcept; | |
| [[nodiscard]] phys::Vec2f toScreenCoords(const phys::Vec2f& v) const; | |
| // File: src/game/resources/TextureManager.cpp | |
| #include "TextureManager.h" | |
| TextureManager::~TextureManager() { | |
| for (const auto& tex : textures | std::views::values) { | |
| SDL_DestroyTexture(tex); | |
| } | |
| } | |
| void TextureManager::clear() noexcept { | |
| for (const auto& tex : textures | std::views::values) { | |
| SDL_DestroyTexture(tex); | |
| } | |
| textures.clear(); | |
| } | |
| SDL_Texture* TextureManager::load(const std::string& key, SDL_Renderer* renderer, const std::string& path) { | |
| // ... existing load implementation ... | |
| } | |
| SDL_Texture* TextureManager::get(const std::string& key) const { | |
| // ... existing get implementation ... | |
| } |
| SDL_Window* window_ {nullptr}; | |
| SDL_Renderer* renderer_ {nullptr}; | |
| TextureManager textureManager_ {}; | |
| double framesPerSecond_ {}; | |
| bool vsyncEnabled_ {true}; | |
| class Camera { public: phys::Vec2f pos {}; } camera_; | |
| void destroy() noexcept; | |
| [[nodiscard]] phys::Vec2f toScreenCoords(const phys::Vec2f& v) const; | |
| // File: src/platform/Graphics.cpp | |
| #include "Graphics.h" | |
| #include <SDL.h> | |
| #include <SDL_ttf.h> | |
| void Graphics::destroy() noexcept { | |
| // Release all textures before tearing down the renderer | |
| textureManager_.clear(); | |
| if (renderer_) { | |
| SDL_DestroyRenderer(renderer_); | |
| renderer_ = nullptr; | |
| } | |
| if (window_) { | |
| SDL_DestroyWindow(window_); | |
| window_ = nullptr; | |
| } | |
| // Close fonts before quitting TTF | |
| if (fpsFont_) { | |
| TTF_CloseFont(fpsFont_); | |
| fpsFont_ = nullptr; | |
| } | |
| TTF_Quit(); | |
| } |
🤖 Prompt for AI Agents
In include/platform/Graphics.h around lines 27 to 35, the TextureManager
currently relies on its destructor to call SDL_DestroyTexture which may run
after the SDL_Renderer has been destroyed; add a public clear() method to
TextureManager (which explicitly iterates and destroys all SDL_Textures and
clears its container) and then update Graphics::destroy() to call
textureManager_.clear() before destroying the renderer and window so textures
are freed while the renderer is still valid.
include/platform/Graphics.h
Outdated
| [[nodiscard]] phys::Vec2f toScreenCoords(const phys::Vec2f& v) const; | ||
| void drawSprite(const Sprite& sprite, const phys::Vec2f& pos) const; | ||
| void drawWorld(const GameState& gameState) const; | ||
| void drawText(std::string_view text, float x, float y) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid function-static font; manage font lifetime as a member to prevent leaks.
drawText currently uses a function-static TTF_Font* in the .cpp, which is never closed with TTF_CloseFont and becomes invalid after TTF_Quit() in destroy(). Move the font into Graphics and close it during destroy().
#include <SDL.h>
+#include <SDL_ttf.h>
...
private:
SDL_Window* window_ {nullptr};
SDL_Renderer* renderer_ {nullptr};
TextureManager textureManager_ {};
double framesPerSecond_ {};
bool vsyncEnabled_ {true};
+ TTF_Font* fpsFont_ {nullptr};
...
- void drawText(std::string_view text, float x, float y) const;
+ void drawText(std::string_view text, float x, float y) const;And update Graphics.cpp accordingly:
// When initializing text rendering:
if (!fpsFont_) {
fpsFont_ = TTF_OpenFont("assets/fonts/Hack-Regular.ttf", 16);
if (!fpsFont_) { std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; return; }
}
// In drawText:
if (fpsFont_) {
if (SDL_Surface* surf = TTF_RenderText_Blended(fpsFont_, std::string(text).c_str(), color)) { ... }
}
// In destroy():
if (fpsFont_) { TTF_CloseFont(fpsFont_); fpsFont_ = nullptr; }
TTF_Quit();🤖 Prompt for AI Agents
In include/platform/Graphics.h around line 38, drawText currently relies on a
function-static TTF_Font* in the .cpp which leaks and can become invalid after
TTF_Quit(); move font ownership into the Graphics class as a member (e.g.,
TTF_Font* fpsFont_ initialized to nullptr), initialize it when setting up text
rendering (open with TTF_OpenFont and log on failure), use that member in
drawText (guarding null), and in Graphics::destroy() close it with
TTF_CloseFont(fpsFont_) and set to nullptr before calling TTF_Quit().
include/platform/InputSource.h
Outdated
| #ifndef IOEVENTS_H | ||
| #define IOEVENTS_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Rename include guard to avoid potential collision with legacy ioevents.h.
The macro IOEVENTS_H likely matches the old inline-header guard. If both ever coexist on include paths, ODR issues can arise. Use a unique guard.
-#ifndef IOEVENTS_H
-#define IOEVENTS_H
+#ifndef SANDBOX2D_INPUTSOURCE_H
+#define SANDBOX2D_INPUTSOURCE_H
...
-#endif //IOEVENTS_H
+#endif // SANDBOX2D_INPUTSOURCE_HAlso applies to: 15-15
🤖 Prompt for AI Agents
In include/platform/InputSource.h around lines 5-6 (and also line 15), the
include guard macro IOEVENTS_H collides with legacy ioevents.h; replace it with
a unique guard (e.g., INCLUDE_PLATFORM_INPUTSOURCE_H or
PLATFORM_INPUTSOURCE_H_INCLUDED) and update the matching #ifndef/#define at the
top and the corresponding #endif comment at the bottom so the macro names match
exactly.
src/game/Game.cpp
Outdated
| #include "../../include/game/Game.h" | ||
| #include "../../include/platform/InputSource.h" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer public include paths and include required system headers
- Use installed-style include paths instead of brittle relative paths.
- SDL APIs and std::clamp are used but <SDL.h> and aren’t included here. Relying on transitive includes is fragile.
-#include "../../include/game/Game.h"
-#include "../../include/platform/InputSource.h"
+#include "game/Game.h"
+#include "platform/InputSource.h"
+#include <SDL.h>
+#include <algorithm>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #include "../../include/game/Game.h" | |
| #include "../../include/platform/InputSource.h" | |
| #include "game/Game.h" | |
| #include "platform/InputSource.h" | |
| #include <SDL.h> | |
| #include <algorithm> |
🤖 Prompt for AI Agents
In src/game/Game.cpp around lines 5 to 7, replace the brittle relative includes
with the public (installed-style) headers and add missing system headers: change
#include "../../include/game/Game.h" and #include
"../../include/platform/InputSource.h" to their public include equivalents (e.g.
#include <game/Game.h> and #include <platform/InputSource.h> or your project’s
configured include prefixes), and add #include <SDL.h> and #include <algorithm>
so SDL APIs and std::clamp are included directly rather than relying on
transitive includes.
src/platform/Graphics.cpp
Outdated
| #include <SDL.h> | ||
| #include <SDL_image.h> | ||
| #include <SDL_ttf.h> | ||
| #include <format> | ||
| #include <iostream> | ||
| #include <string_view> | ||
| #include <cmath> | ||
|
|
||
| bool Graphics::init(const int winW, const int winH, std::string_view windowTitle) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing headers for std::format and std::exp
std::format and std::exp are used but and are not included, which will fail to compile on stricter toolchains.
Apply:
#include <SDL_ttf.h>
#include <iostream>
#include <string_view>
+#include <format>
+#include <cmath>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #include <SDL.h> | |
| #include <SDL_image.h> | |
| #include <SDL_ttf.h> | |
| #include <format> | |
| #include <iostream> | |
| #include <string_view> | |
| #include <cmath> | |
| bool Graphics::init(const int winW, const int winH, std::string_view windowTitle) { | |
| #include <SDL.h> | |
| #include <SDL_image.h> | |
| #include <SDL_ttf.h> | |
| #include <iostream> | |
| #include <string_view> | |
| #include <format> | |
| #include <cmath> | |
| bool Graphics::init(const int winW, const int winH, std::string_view windowTitle) { | |
| // ... | |
| } |
🤖 Prompt for AI Agents
In src/platform/Graphics.cpp around lines 9 to 14, the file uses std::format and
std::exp but does not include the required headers; add #include <format> for
std::format and #include <cmath> for std::exp (and ensure the project is
compiled with a C++20 or newer standard so <format> is available).
src/platform/InputSource.cpp
Outdated
| #include "platform/InputSource.h" | ||
| #include "game/GameState.h" | ||
|
|
||
| #include <iostream> | ||
|
|
||
| inline void onKeyDown(GameState& state, const SDL_Keysym* keysym) { | ||
| void onKeyDown(GameState &state, const SDL_Keysym *keysym) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing headers for std::format and std::sqrt
Both are used; add the proper standard headers to avoid build breaks on stricter toolchains.
#include "platform/InputSource.h"
#include "game/GameState.h"
#include <iostream>
+#include <format>
+#include <cmath>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #include "platform/InputSource.h" | |
| #include "game/GameState.h" | |
| #include <iostream> | |
| inline void onKeyDown(GameState& state, const SDL_Keysym* keysym) { | |
| void onKeyDown(GameState &state, const SDL_Keysym *keysym) { | |
| #include "platform/InputSource.h" | |
| #include "game/GameState.h" | |
| #include <iostream> | |
| #include <format> | |
| #include <cmath> | |
| void onKeyDown(GameState &state, const SDL_Keysym *keysym) { |
🤖 Prompt for AI Agents
In src/platform/InputSource.cpp around lines 5 to 10, the file uses std::format
and std::sqrt but does not include their headers; add the missing standard
headers by including <format> for std::format and <cmath> for std::sqrt near the
other includes so the code compiles on stricter toolchains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/platform/sdl/InputSource.cpp (1)
73-89: Add missing<cmath>includeTo use
std::sqrt, you need to include the C++ math header. Insrc/platform/sdl/InputSource.cpp, add:#include <SDL.h> +#include <cmath> void InputSource::handlePlayerInput(GameState &state) { // … if (const float len = std::sqrt(dx*dx + dy*dy); len > 0) { dx /= len; dy /= len; } // … }No change needed to the Player API call
Your grep output already shows thatPlayer::set_velocity(phys::Vec2f)is declared ininclude/game/Player.hand implemented insrc/game/Player.cpp, matching the use ofstate.player.set_velocity(...)inInputSource.cpp.
♻️ Duplicate comments (1)
src/game/Game.cpp (1)
8-8: Include for std::clamp (repeat from previous review)std::clamp is used later; add the header to avoid relying on transitive includes.
Apply:
#include <chrono> +#include <algorithm>
🧹 Nitpick comments (37)
include/game/physics/Vec2.h (2)
27-29: Make tensorDot const; consider clearer naming (Hadamard).This method doesn’t mutate state and should be callable on const Vec2. Also, "tensorDot" is a misnomer for element-wise product; consider "hadamard" or "cwiseMul" to avoid confusion.
Apply minimal fix (const + [[nodiscard]] + noexcept):
- constexpr Vec2 tensorDot(const Vec2& other) { - return {x * other.x, y * other.y}; - } + [[nodiscard]] constexpr Vec2 tensorDot(const Vec2& other) const noexcept { + return {x * other.x, y * other.y}; + }
55-57: Guard division by zero for integral T.For integral T, division by zero is UB. Add a debug-time guard (keeps fast builds unchanged).
- constexpr Vec2 operator/(T scalar) const { - return {x / scalar, y / scalar}; - } + constexpr Vec2 operator/(T scalar) const { + if constexpr (std::is_integral_v<T>) { + assert(scalar != 0 && "Vec2::operator/: division by zero for integral type"); + } + return {x / scalar, y / scalar}; + }You’ll need this include near the other headers:
#include <cmath> #include <type_traits> +#include <cassert>include/platform/sdl/SDLContext.h (2)
43-46: Copy is deleted — also delete move or explicitly allow it.Given ownership of process-wide SDL state, moving is not meaningful. You already delete copy; delete move as well to avoid accidental transfers.
SDLContext(const SDLContext&) = delete; SDLContext& operator=(const SDLContext&) = delete; + SDLContext(SDLContext&&) = delete; + SDLContext& operator=(SDLContext&&) = delete;
12-12: Consider centralizing all SDL subsystem init here (including EVENTS) and removing it from InputSource.To avoid split ownership, prefer a single owner (SDLContext) to initialize SDL subsystems (VIDEO, EVENTS, IMAGE, TTF). Then make InputSource purely an event pump.
include/game/input/InputManager.h (1)
10-11: Removeconstfrominit()andhandleEvents()in both declaration and definitionThe verification shows that the methods are still
const-qualified in the implementation, so the refactor hasn’t been applied:
- Header:
include/game/input/InputManager.h
• Line 10: change- bool init() const;
- bool init();
• Line 11: change
```diff
- void handleEvents(GameState& state) const;
- void handleEvents(GameState& state);
- Implementation:
src/game/input/InputManager.cpp
• Line 7: change- bool InputManager::init() const {
- bool InputManager::init() {
• Line 11: change ```diff
- void InputManager::handleEvents(GameState &state) const {
- void InputManager::handleEvents(GameState &state) {
Removing the
constspecifiers allowsInputManagerto maintain internal state (such as event filters or input mappings) across calls.include/game/Player.h (2)
5-7: Avoid heavy/recursive includes: include only what you usePlayer doesn’t need World.h; pulling it in increases compile times and risks cycles. Include the minimal headers that define the used types.
#pragma once -#include "physics/Vec2.h" -#include "world/World.h" +#include "physics/Vec2.h" +#include "resources/Sprite.h" +#include "Config.h"This keeps Sprite and config::resources::PLAYER_TEX available without dragging World in.
12-12: Drop redundant std::string construction in sprite initializerSprite::textureName is a std::string; it will implicitly construct from const char*. Avoid the extra allocation call site.
- Sprite sprite {std::string(config::resources::PLAYER_TEX)}; + Sprite sprite {config::resources::PLAYER_TEX};include/game/graphics/Renderer.h (2)
5-7: Soften compile-time coupling in the header; long-term, abstract the graphics deviceRight now the game header includes both GameState and the SDL-backed GraphicsDevice. Immediate win: forward-declare the model types and remove the GameState include from this header; implementation can include the heavy headers. Longer term: depend on an IGraphicsDevice interface and store a pointer/unique_ptr in Renderer to fully remove the game header’s dependency on the SDL backend.
Minimal header-only change:
#pragma once -#include "game/GameState.h" -#include "platform/sdl/GraphicsDevice.h" +#include "platform/sdl/GraphicsDevice.h" // keep for now +struct GameState; +struct World; +struct Sprite;Optional next step (bigger change, recommended):
- Introduce include/game/graphics/IGraphicsDevice.h (pure virtual).
- Change
GraphicsDevice graphicsDevice_{}tostd::unique_ptr<IGraphicsDevice> graphicsDevice_;and inject an SDLGraphicsDevice from platform/main.Also applies to: 13-23
10-12: Add a defensive assertion fordeltaSecondsinRenderer::renderI verified that in
src/game/Game.cppyou already clampdeltaSecondsto a minimum of 1⁄500 s and a maximum of 0.25 s before callingrenderer_.render(...), so a divide-by-zero cannot occur in your main loop. However, it’s still prudent to guard against regressions (e.g. future call sites that forget the clamp).• In
src/game/Game.cpp(around lines 40–47), you have:constexpr double MIN_DELTA_SECONDS = 1.0/500.0; constexpr double MAX_DELTA_SECONDS = 0.25; ... deltaSeconds = std::clamp(deltaSeconds, MIN_DELTA_SECONDS, MAX_DELTA_SECONDS); renderer_.render(gameState_, deltaSeconds);• In
include/game/graphics/Renderer.h, the signature remains:bool init(int winW, int winH, std::string_view windowTitle); void render(const GameState& gameState, double deltaSeconds);Consider adding an assertion at the top of the implementation (
Renderer.cpp) to catch any unintended zero or negativedeltaSecondsin debug builds:void Renderer::render(const GameState& gameState, double deltaSeconds) { + assert(deltaSeconds > 0 && "deltaSeconds must be positive (clamp missed upstream)"); // existing rendering logic... }This simple check documents the precondition and will immediately flag any future regressions where
rendermight be called without proper clamping.include/platform/sdl/Resource.h (2)
11-27: Strengthen the RAII wrapper API and make semantics explicitNice lightweight wrapper. A few small improvements:
- Make move operations explicit and delete copy to avoid surprises.
- Provide reset()/release() to integrate with SDL APIs that hand back ownership.
- Simplify operator bool.
Apply:
template<typename T, auto Deleter> class Resource { using ResourcePtr = std::unique_ptr<T, decltype(Deleter)>; public: explicit Resource(T* resource = nullptr) : resource_(resource, Deleter) {} - explicit operator bool() const { return resource_ != nullptr; } + // Non-copyable, movable RAII + Resource(const Resource&) = delete; + Resource& operator=(const Resource&) = delete; + Resource(Resource&&) noexcept = default; + Resource& operator=(Resource&&) noexcept = default; + + explicit operator bool() const { return static_cast<bool>(resource_); } [[nodiscard]] T* get() const { return resource_.get(); } T* operator->() const { return resource_.get(); } T& operator*() const { return *resource_; } + + // Ownership helpers + T* release() noexcept { return resource_.release(); } + void reset(T* ptr = nullptr) noexcept { resource_.reset(ptr); } private: ResourcePtr resource_; };
7-10: Separate generic wrapper from SDL-specific aliases to reduce header weightResource<T,Deleter> itself doesn’t need SDL headers; only the aliases do. Consider moving the two using-aliases to a small include/platform/sdl/SDLResources.h that includes SDL headers, while this header only includes . This reduces rebuild churn across the codebase.
Also applies to: 29-30
include/game/GameState.h (1)
4-7: Add missing cstdint include and tighten API contract
- uint64_t requires ; include it here to avoid hidden dependencies.
- Mark unpaused() [[nodiscard]] and noexcept (pure query, no throws).
#pragma once #include "Player.h" #include "world/World.h" +#include <cstdint> struct GameState { uint64_t gameTicks = 0; bool running = true; bool paused = true; bool debugMode = true; Player player {}; World world {}; - bool unpaused() const { return !paused; } + [[nodiscard]] bool unpaused() const noexcept { return !paused; } };Also applies to: 16-17
src/platform/sdl/AssetLoader.cpp (3)
8-14: Missing standard headers; remove unused includestd::array and std::format are used, but and aren’t included. is included but unused.
Apply:
#include <SDL_ttf.h> #include <filesystem> #include <iostream> -#include <ranges> +#include <array> +#include <format>
50-56: Report loading outcome for “essential” assetsloadFonts()/loadTextures() silently continue on failures. Since GraphicsDevice unconditionally draws text and textures later, consider returning a boolean and let callers fail-fast if essentials aren’t available.
If you agree, I can propose a small API tweak returning bool and thread it through GraphicsDevice::init.
15-23: Reduce coupling: AssetLoader shouldn’t know game resource namesIncluding game/Config.h and hardcoding “essentials” (PLAYER_TEX, WALL_TEX, FONT) in the platform layer increases cross-layer coupling. Prefer:
- Move resource name constants to a neutral “core/resources” module, or
- Let the game layer decide what to load and call AssetLoader::loadTexture/loadFont with names, or
- Inject a list of resources to load into GraphicsDevice/AssetLoader, keeping platform generic.
I can sketch an interface where Renderer (game) provides the list of assets to load to the platform at init.
src/platform/sdl/InputSource.cpp (2)
51-71: Tight platform→game coupling in event handlingonKeyDown/onMouse* directly mutate GameState and Player. For a cleaner separation:
- Translate SDL events into platform-agnostic InputEvents and enqueue them, or
- Provide callbacks (std::function) to the game layer that consume high-level inputs.
This avoids platform including game/GameState.h and makes testing easier.I can propose a minimal InputEvent enum + queue and a thin adapter in InputManager to update GameState.
51-71: Minor: simplify event loop; avoid raw pointersYou create SDL_Event e then take a pointer to it. Using references reduces noise and accidental null checks.
Apply:
- SDL_Event e; - SDL_Event* ePtr = &e; - while (SDL_PollEvent(ePtr)) { - switch (ePtr->type) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { case SDL_QUIT: state.running = false; break; case SDL_KEYDOWN: - onKeyDown(state, &ePtr->key.keysym); + onKeyDown(state, &e.key.keysym); break; case SDL_MOUSEBUTTONDOWN: - onMouseButtonDown(state, &ePtr->button); + onMouseButtonDown(state, &e.button); break; case SDL_MOUSEMOTION: - onMouseMotion(state, &ePtr->motion); + onMouseMotion(state, &e.motion); break; default: ; } }src/game/graphics/Renderer.cpp (4)
5-5: Add missing headers for std::exp and std::formatThis TU uses std::exp and std::format but doesn’t include or . Avoid relying on transitive includes.
Apply:
#include "game/graphics/Renderer.h" +#include <cmath> +#include <format>
12-24: Consider view frustum culling for world drawYou iterate over all CHUNK_COUNT tiles every frame. For larger worlds, add simple AABB culling in world space against the camera viewport to cut draw calls.
I can provide a small helper to compute visible chunk/tile ranges based on camera_.pos and window size.
30-37: Camera follow: good smoothing; clamp snapFactor for extreme dtFor very large dt, snapFactor can become 1 due to numeric limits. Clamping to [0,1] is safer (even if practically fine).
Apply:
- const float snapFactor = 1.0f - std::exp(-CAMERA_STIFFNESS * dt); + const float snapFactor = std::clamp(1.0f - std::exp(-CAMERA_STIFFNESS * dt), 0.0f, 1.0f);(Remember to include if you take this.)
45-50: Guard FPS formatting against very small deltaSecondsdeltaSeconds is clamped by Game to >= 1/500, so this is practically safe. If Renderer is reused elsewhere, consider guarding against 0 to avoid inf/nan display.
- graphicsDevice_.drawText(std::format("FPS: {:.0f}", 1.0 / deltaSeconds), 10, 10); + const double fps = (deltaSeconds > 0.0) ? (1.0 / deltaSeconds) : 0.0; + graphicsDevice_.drawText(std::format("FPS: {:.0f}", fps), 10, 10);src/game/Game.cpp (1)
24-26: Avoid duplicating step logic; delegate to GameStateGameState already exposes step(deltaSeconds). Call that instead of reaching into Player here.
Apply:
-void Game::step(double deltaSeconds) { - gameState_.player.move(deltaSeconds); -} +void Game::step(double deltaSeconds) { + gameState_.step(deltaSeconds); +}include/platform/sdl/GraphicsDevice.h (3)
6-12: Public header couples game to SDL platformGame’s Renderer depends on include/platform/sdl/GraphicsDevice.h, which pulls in SDLContext and AssetLoader and lives under a platform-specific path. This undermines the “separate platform API” goal.
Recommended:
- Define a platform-agnostic IRenderDevice (or IRenderer) in include/engine/… (beginFrame/endFrame/drawTexture/drawText/getWindowSize).
- Make GraphicsDevice (SDL) implement that interface under src/platform/sdl and keep it out of public includes.
- Renderer depends only on the interface and is constructed with a reference/pointer to it (dependency injection).
I can sketch the interface and a minimal factory if you’d like.
15-22: Clarify init/drawText failure contractsinit currently returns bool but doesn’t expose error info; drawText doesn’t specify behavior when the font is missing. Consider returning an error or documenting that missing fonts are no-ops.
If helpful, I can refactor init to return std::optionalstd::string error or std::expected<void, std::string>.
23-32: SDL lifetime split across GraphicsDevice/InputSource; unify ownershipGraphicsDevice owns SDLContext (video/IMG/TTF). InputSource separately initializes the EVENTS subsystem. Prefer centralizing all SDL init in SDLContext (including EVENTS) and making InputSource a pure consumer.
I can extend SDLContext to init EVENTS and remove init/teardown from InputSource.
include/platform/sdl/AssetLoader.h (3)
7-7: Enable heterogeneous lookup to avoid temporary string allocations in gettersgetTexture/getFont currently construct std::string keys for find(). Use transparent hashing to allow std::string_view lookups without allocation. This is small but pervasive.
#include <string> +#include <functional> #include <string_view> #include <unordered_map> @@ - std::unordered_map<std::string, Texture> textures; - std::unordered_map<std::string, Font> fonts; + using string_hash = std::hash<std::string_view>; + using string_eq = std::equal_to<>; + std::unordered_map<std::string, Texture, string_hash, string_eq> textures; + std::unordered_map<std::string, Font, string_hash, string_eq> fonts;Then in AssetLoader.cpp you can do
textures.find(textureName)directly.Also applies to: 22-23
5-6: Optional: forward-declare SDL_Renderer to reduce header surfaceSince the header only uses SDL_Renderer*, a forward declaration avoids dragging SDL headers into all includers (Resource.h might already pull them in, but this keeps it lighter).
#pragma once +struct SDL_Renderer; + #include "platform/sdl/Resource.h"
15-16: Consider returning pointers (or optional) instead of references to “empty” singletonsReturning a reference to a static “empty” Texture/Font works but can hide errors. Returning
const Texture*/const Font*(orstd::optional<std::reference_wrapper<const Texture>>) would make missing assets explicit at call sites.include/game/Config.h (3)
6-6: Verify include path consistency for Vec2This header includes "physics/Vec2.h", while elsewhere it appears under "include/game/physics/Vec2.h". If your include dirs don’t add include/game/, this may fail on some setups/IDEs. Consider using "game/physics/Vec2.h" for consistency.
28-28: Use a float literal for DRAW_SCALEMinor correctness/numerics nit: use 4.0f to avoid needless double-to-float conversion.
- inline constexpr float DRAW_SCALE = 4.0; + inline constexpr float DRAW_SCALE = 4.0f;
14-22: Config layering: split resources vs graphics (follow-up to platform separation)To keep responsibilities tight and support headless/tests, consider splitting
config::resources(data paths/names) fromconfig::graphics(window/tile/scaling). This will also help when injecting a non-SDL renderer later.Also applies to: 24-29
src/platform/sdl/GraphicsDevice.cpp (3)
25-34: Prefer creating the renderer with PRESENTVSYNC; fall back cleanly if unavailableSetting vsync post-creation via SDL_RenderSetVSync requires SDL 2.0.18+. Safer path: try
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNCfirst; on failure, retry without vsync and setvsyncEnabled_ = false.- renderer_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED); - if (!renderer_) { - std::cerr << std::format("SDL_CreateRenderer error: {}\n", SDL_GetError()); - return false; - } - - if (SDL_RenderSetVSync(renderer_, 1) != 0){ - std::cerr << std::format("Could not set vsync. Error: {}\n", SDL_GetError()); - vsyncEnabled_ = false; - } + int flags = SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC; + renderer_ = SDL_CreateRenderer(window_, -1, flags); + if (!renderer_) { + std::cerr << std::format("SDL_CreateRenderer (vsync) error: {}\n", SDL_GetError()); + renderer_ = SDL_CreateRenderer(window_, -1, SDL_RENDERER_ACCELERATED); + if (!renderer_) { + std::cerr << std::format("SDL_CreateRenderer error: {}\n", SDL_GetError()); + return false; + } + vsyncEnabled_ = false; + } else { + vsyncEnabled_ = true; + }Also confirm your minimum SDL2 version if keeping SDL_RenderSetVSync.
4-6: Platform layer includes game Sprite type — consider inverting the dependency
GraphicsDevice(SDL/platform) depends ongame/resources/Sprite.h, which tightens coupling. To align with the PR’s separation goal, consider:
- Moving drawTexture to take a platform-agnostic descriptor (e.g., texture name + src rect), or
- Having a game-side Renderer translate Sprite → Texture+rect and call platform via an abstract IGraphicsDevice.
49-54: Optional: replace fixed 5 ms sleep with a target frame pacingIf vsync is off, a fixed 5 ms sleep yields ~200 FPS and variable load. Consider pacing to a target (e.g., 60 FPS: sleep max(0, 16.6ms - frameTime)).
include/game/world/World.h (3)
28-29: Bounds and error handling on getTileEnsure the implementation enforces bounds (as in the prior code), logging and throwing on invalid access. If performance is a concern, consider a
getTileUnsafeinternal helper.
31-39: Const-correct accessors and noexcept where applicableConsider adding const overloads for accessors (e.g.,
const Tile& getTileGlobal(...) const) andnoexcepton helpers that can’t throw (e.g.,chunkCoords,tileCoordsalready are). This improves API clarity.
11-17: Naming: CHUNK_SIZE vs TILE_SIZE (Config) — clarify semanticsYou have
world::CHUNK_SIZE(tiles per chunk) andconfig::graphics::TILE_SIZE(pixels per tile). Consider a brief comment near CHUNK_SIZE to prevent conflating the two.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (22)
include/game/Config.h(1 hunks)include/game/Game.h(1 hunks)include/game/GameState.h(1 hunks)include/game/Player.h(1 hunks)include/game/graphics/Renderer.h(1 hunks)include/game/input/InputManager.h(1 hunks)include/game/physics/Vec2.h(1 hunks)include/game/resources/Sprite.h(1 hunks)include/game/world/World.h(1 hunks)include/platform/sdl/AssetLoader.h(1 hunks)include/platform/sdl/GraphicsDevice.h(1 hunks)include/platform/sdl/InputSource.h(1 hunks)include/platform/sdl/Resource.h(1 hunks)include/platform/sdl/SDLContext.h(1 hunks)src/game/Game.cpp(1 hunks)src/game/graphics/Renderer.cpp(1 hunks)src/game/input/InputManager.cpp(1 hunks)src/game/world/World.cpp(1 hunks)src/platform/main.cpp(1 hunks)src/platform/sdl/AssetLoader.cpp(1 hunks)src/platform/sdl/GraphicsDevice.cpp(1 hunks)src/platform/sdl/InputSource.cpp(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/platform/main.cpp
- include/game/Game.h
- src/game/world/World.cpp
🧰 Additional context used
🧬 Code graph analysis (19)
include/game/resources/Sprite.h (3)
src/core/resources/TextureManager.h (2)
struct Sprite {(16-18)height(19-20)src/core/GameState.h (2)
sprite(22-26)WORLD_SIZE(15-19)src/core/world/World.h (1)
struct Tile {(21-23)
include/game/physics/Vec2.h (1)
src/core/physics/Vec2.h (3)
phys(11-22)x(16-16)operator(58-60)
src/game/input/InputManager.cpp (3)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/platform/sdl/InputSource.cpp (4)
init(11-17)init(11-11)handleEvents(51-71)handleEvents(51-51)src/core/ioevents.h (1)
handlePlayerInput(63-79)
include/game/Player.h (2)
src/game/Player.cpp (4)
set_velocity(8-10)set_velocity(8-8)move(12-15)move(12-12)src/core/resources/TextureManager.h (1)
struct Sprite {(16-18)
include/platform/sdl/SDLContext.h (3)
src/core/Graphics.h (2)
vsyncEnabled_(21-58)destroy(32-44)src/core/Graphics.cpp (1)
init(16-58)src/main.cpp (1)
main(39-50)
include/game/input/InputManager.h (4)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/input/InputManager.cpp (4)
init(7-9)init(7-7)handleEvents(11-14)handleEvents(11-11)src/platform/sdl/InputSource.cpp (4)
init(11-17)init(11-11)handleEvents(51-71)handleEvents(51-51)src/core/ioevents.h (1)
handlePlayerInput(63-79)
include/game/graphics/Renderer.h (5)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/graphics/Renderer.cpp (16)
init(7-10)init(7-7)render(52-66)render(52-52)drawSprite(68-71)drawSprite(68-68)drawWorld(12-24)drawWorld(12-12)cameraSnap(26-28)cameraSnap(26-26)cameraFollow(30-37)cameraFollow(30-30)toScreenCoords(39-43)toScreenCoords(39-39)drawDebugInfo(45-50)drawDebugInfo(45-45)src/platform/sdl/GraphicsDevice.cpp (2)
init(13-41)init(13-13)include/game/GameState.h (1)
world(14-14)include/game/world/World.h (1)
world(10-53)
src/game/graphics/Renderer.cpp (5)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/platform/sdl/GraphicsDevice.cpp (2)
init(13-41)init(13-13)include/game/graphics/Renderer.h (1)
graphicsDevice_(21-21)include/game/world/World.h (2)
world(10-53)CHUNK_COUNT(31-46)src/core/Graphics.cpp (4)
draw(145-171)drawWorld(105-117)init(16-58)drawSprite(89-103)
include/platform/sdl/InputSource.h (2)
src/platform/sdl/InputSource.cpp (7)
InputSource(91-93)init(11-17)init(11-11)handleEvents(51-71)handleEvents(51-51)handlePlayerInput(73-89)handlePlayerInput(73-73)src/game/input/InputManager.cpp (4)
init(7-9)init(7-7)handleEvents(11-14)handleEvents(11-11)
include/game/Config.h (3)
include/game/world/World.h (1)
Vec2i(14-18)src/core/GameState.h (1)
WORLD_SIZE(15-19)src/main.cpp (1)
main(39-50)
src/platform/sdl/GraphicsDevice.cpp (6)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/graphics/Renderer.cpp (2)
init(7-10)init(7-7)include/platform/sdl/GraphicsDevice.h (2)
beginFrame(16-27)GraphicsDevice(13-32)include/game/resources/Sprite.h (1)
height(13-14)src/core/Graphics.cpp (5)
init(16-58)draw(145-171)drawText(119-143)drawSprite(89-103)drawPlaceholder(71-80)src/core/resources/TextureManager.h (2)
TextureManager(22-38)struct Sprite {(16-18)
include/platform/sdl/AssetLoader.h (3)
src/platform/sdl/AssetLoader.cpp (12)
loadTextures(15-23)loadTextures(15-15)loadFonts(50-56)loadFonts(50-50)getTexture(42-48)getTexture(42-42)getFont(67-73)getFont(67-67)loadTexture(25-40)loadTexture(25-25)loadFont(58-65)loadFont(58-58)include/platform/sdl/Resource.h (1)
nodiscard(20-20)include/game/Config.h (1)
string_view(21-22)
include/platform/sdl/GraphicsDevice.h (4)
src/platform/sdl/GraphicsDevice.cpp (15)
GraphicsDevice(95-104)init(13-41)init(13-13)beginFrame(43-47)beginFrame(43-43)endFrame(49-54)endFrame(49-49)drawTexture(106-119)drawTexture(106-106)drawText(68-79)drawText(68-68)drawOverlay(81-87)drawOverlay(81-81)getWindowSize(89-93)getWindowSize(89-89)src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/graphics/Renderer.cpp (2)
init(7-10)init(7-7)include/platform/sdl/SDLContext.h (1)
SDLContext(12-46)
src/platform/sdl/AssetLoader.cpp (3)
src/core/resources/TextureManager.h (1)
TextureManager(22-38)src/core/resources/TextureManager.cpp (4)
loadTextures(19-27)loadTexture(29-45)TextureManager(13-17)getTexture(47-52)src/core/Graphics.cpp (2)
drawText(119-143)init(16-58)
src/game/Game.cpp (9)
src/game/graphics/Renderer.cpp (2)
init(7-10)init(7-7)src/game/input/InputManager.cpp (2)
init(7-9)init(7-7)src/game/world/World.cpp (2)
init(16-29)init(16-16)src/platform/sdl/GraphicsDevice.cpp (2)
init(13-41)init(13-13)src/platform/sdl/InputSource.cpp (2)
init(11-17)init(11-11)include/game/Game.h (2)
renderer_(17-17)inputManager_(18-18)src/main.cpp (2)
loop(8-37)main(39-50)src/core/GameState.h (2)
deltaSeconds(28-35)sprite(22-26)src/core/GameState.cpp (2)
step(16-18)move(11-14)
include/platform/sdl/Resource.h (3)
src/core/resources/TextureManager.h (1)
TextureManager(22-38)src/core/resources/TextureManager.cpp (1)
TextureManager(13-17)src/core/Graphics.h (1)
destroy(32-44)
include/game/GameState.h (3)
include/game/world/World.h (1)
world(10-53)src/core/GameState.h (3)
deltaSeconds(28-35)WORLD_SIZE(15-19)sprite(22-26)src/main.cpp (2)
main(39-50)loop(8-37)
include/game/world/World.h (6)
include/game/GameState.h (1)
world(14-14)include/game/Config.h (1)
Vec2i(26-29)include/game/physics/Vec2.h (2)
nodiscard(21-25)phys(8-19)src/core/world/World.h (6)
chunks(37-50)tiles(25-32)chunks(37-61)nodiscard(71-73)nodiscard(79-84)nodiscard(75-77)src/core/GameState.h (1)
WORLD_SIZE(15-19)src/core/Graphics.cpp (1)
drawWorld(105-117)
src/platform/sdl/InputSource.cpp (4)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/input/InputManager.cpp (4)
init(7-9)init(7-7)handleEvents(11-14)handleEvents(11-11)include/platform/sdl/InputSource.h (1)
InputSource(8-15)src/core/Graphics.h (1)
vsyncEnabled_(21-58)
🔇 Additional comments (7)
include/game/physics/Vec2.h (1)
1-68: No duplicate Vec2 definitions foundI searched the entire repository and found only one
Vec2.hatinclude/game/physics/Vec2.h. There are no otherVec2<T>template definitions or additionalVec2.hheaders to consolidate. The existing includes (insrc/game/Player.cpp,include/game/Player.h, andinclude/game/Config.h) all refer to this single header. The original consolidation suggestion does not apply.Likely an incorrect or invalid review comment.
include/platform/sdl/InputSource.h (1)
6-6: Forward-declaring GameState is good — keeps platform headers light.No action needed. Nice decoupling from game internals.
include/game/resources/Sprite.h (2)
10-14: Simple POD Sprite looks good.Clear, data-only container suitable for transport between game and renderer.
5-14: No duplicateSpritedefinitions detectedI searched the entire codebase for other
struct Spritedeclarations and only found the one ininclude/game/resources/Sprite.h. There are no additionalSpritetypes to consolidate, so you can safely ignore the original suggestion.Likely an incorrect or invalid review comment.
src/game/graphics/Renderer.cpp (1)
68-71: Vec2f is a valid aggregate—no changes requiredThe
phys::Vec2<T>template is declared as a plainstructwith only public data members (x,y) and no user-provided constructors. In C++17 and later, this qualifies it as an aggregate, so structured bindings (auto [x,y] = …) are both legal and safe. You can leave the code as-is without refactoring.src/game/Game.cpp (1)
38-61: Main loop looks solidFixed timestep with clamped delta, pause-aware stepping, and decoupled render tick. Nice.
src/platform/sdl/GraphicsDevice.cpp (1)
56-66: LGTM on placeholder renderingClear, visible magenta/white checker to surface missing textures.
| // | ||
|
|
||
| #pragma once | ||
| #include "platform/sdl/InputSource.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was initially using abstract interfaces but it seemed overkill, the current refactor already positions the code with much better separation that would make further steps easier. Since the code is still developing, going the full way might be premature. Do you have suggestions to ease into that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jacopouggeri That's a very pragmatic approach! You're right that premature abstraction can be more hindrance than help during active development. Let me suggest some incremental steps that ease toward better separation without requiring a major overhaul:
Phase 1: Minimize the include footprint (immediate, low-risk)
// In InputManager.h - just move the include to the .cpp file
#pragma once
struct GameState; // forward declaration
class InputSource; // forward declaration
class InputManager {
public:
bool init() const;
void handleEvents(GameState& state) const;
private:
InputSource* inputSource_; // pointer instead of value
};This keeps the same concrete type but removes the header dependency. The implementation stays in the .cpp file where platform headers are fine.
Phase 2: Simple factory approach (when you add another platform)
When you eventually want to support another platform (or just better testability), you can introduce a simple factory function in the platform layer:
// platform/InputSourceFactory.h
std::unique_ptr<InputSource> createInputSource();Phase 3: Interface when you actually need it
Only introduce IInputSource when you have a concrete second implementation or testing need.
The key insight is that your current directory structure already provides the right boundaries - you just need to be more careful about include dependencies crossing those boundaries. Would this incremental approach work better for your current development pace?
| void drawTexture(const Sprite &sprite, float screenX, float screenY) const; | ||
| void drawText(std::string_view text, float x, float y) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug in implementation: srcRect uses scaled size (in .cpp)
In GraphicsDevice.cpp, srcRect width/height are computed after applying DRAW_SCALE. srcRect should use the unscaled sprite pixel size; only destRect should be scaled. This causes incorrect sampling/cropping.
Apply in src/platform/sdl/GraphicsDevice.cpp:
- const float width = sprite.width * config::graphics::DRAW_SCALE;
- const float height = sprite.height * config::graphics::DRAW_SCALE;
- const SDL_Rect srcRect {0, 0, static_cast<int>(width), static_cast<int>(height)};
- screenX -= width / 2.0f;
- screenY -= height / 2.0f;
- const SDL_FRect destRect {screenX, screenY, width, height};
+ const int srcW = sprite.width;
+ const int srcH = sprite.height;
+ const float dstW = srcW * config::graphics::DRAW_SCALE;
+ const float dstH = srcH * config::graphics::DRAW_SCALE;
+ const SDL_Rect srcRect {0, 0, srcW, srcH};
+ screenX -= dstW / 2.0f;
+ screenY -= dstH / 2.0f;
+ const SDL_FRect destRect {screenX, screenY, dstW, dstH};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void drawTexture(const Sprite &sprite, float screenX, float screenY) const; | |
| void drawText(std::string_view text, float x, float y) const; | |
| // Before: both srcRect and destRect used scaled width/height | |
| - const float width = sprite.width * config::graphics::DRAW_SCALE; | |
| - const float height = sprite.height * config::graphics::DRAW_SCALE; | |
| - const SDL_Rect srcRect {0, 0, static_cast<int>(width), static_cast<int>(height)}; | |
| - screenX -= width / 2.0f; | |
| - screenY -= height / 2.0f; | |
| - const SDL_FRect destRect {screenX, screenY, width, height}; | |
| // After: use unscaled sprite dimensions for srcRect; scale only destRect | |
| const int srcW = sprite.width; | |
| const int srcH = sprite.height; | |
| const float dstW = srcW * config::graphics::DRAW_SCALE; | |
| const float dstH = srcH * config::graphics::DRAW_SCALE; | |
| const SDL_Rect srcRect { 0, 0, srcW, srcH }; | |
| screenX -= dstW / 2.0f; | |
| screenY -= dstH / 2.0f; | |
| const SDL_FRect destRect { screenX, screenY, dstW, dstH }; |
🤖 Prompt for AI Agents
In include/platform/sdl/GraphicsDevice.h around lines 18 to 19 and corresponding
src/platform/sdl/GraphicsDevice.cpp, the bug is that srcRect is being computed
using the scaled size (DRAW_SCALE) causing wrong sampling/cropping; compute
srcRect using the sprite's raw pixel dimensions (unscaled width/height or frame
size) and only apply DRAW_SCALE to destRect (position and size on screen).
Update the implementation to build srcRect from sprite pixel coordinates/size
(no DRAW_SCALE), then set destRect.x/y/width/height = screenX/screenY and sprite
size multiplied by DRAW_SCALE, and ensure types match SDL_Rect/SDL_FRect
expectations.
| void drawText(std::string_view text, float x, float y) const; | ||
| void drawOverlay(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle missing font in drawText (in .cpp)
drawText dereferences a potentially null font. Guard against it to avoid crashes if asset loading failed.
Apply in src/platform/sdl/GraphicsDevice.cpp:
- TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get();
- if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) {
+ TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get();
+ if (!font) return;
+ if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void drawText(std::string_view text, float x, float y) const; | |
| void drawOverlay(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const; | |
| // In src/platform/sdl/GraphicsDevice.cpp, inside GraphicsDevice::drawText(...) | |
| TTF_Font* font = assetLoader_.getFont(config::resources::FONT).get(); | |
| if (!font) | |
| return; | |
| if (const auto textSurface = TTF_RenderText_Blended(font, text.data(), color)) { | |
| // existing rendering logic… | |
| } |
🤖 Prompt for AI Agents
In include/platform/sdl/GraphicsDevice.h lines 19-20 and corresponding
implementation in src/platform/sdl/GraphicsDevice.cpp, drawText currently
dereferences a possibly-null font; update the implementation to check the font
pointer (or font resource handle) before using it and early-return (or skip
rendering) if it is null, optionally logging a warning/error; ensure no
dereference occurs, and preserve function constness and behavior when a valid
font exists (i.e., proceed with the existing draw steps).
include/platform/sdl/InputSource.h
Outdated
| static bool init(); | ||
| static void handleEvents(GameState& state); | ||
| static void handlePlayerInput(GameState& state); | ||
|
|
||
| ~InputSource(); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Static API + non-static destructor mixes lifetimes; convert to RAII or go fully static.
Right now init/handle* are static, but ~InputSource() performs teardown (SDL_QuitSubSystem). That’s inconsistent and can lead to unpredictable ownership and double init/quit when combined with SDLContext.
Preferred: make this a small RAII handle (non-static API) and keep all SDL events lifetime in one place.
class InputSource final {
public:
- static bool init();
- static void handleEvents(GameState& state);
- static void handlePlayerInput(GameState& state);
-
- ~InputSource();
+ InputSource() = default;
+ ~InputSource() noexcept;
+
+ bool init() noexcept; // internally SDL_InitSubSystem(SDL_INIT_EVENTS)
+ void handleEvents(GameState& state) const;
+ void handlePlayerInput(GameState& state) const;
+
+ InputSource(const InputSource&) = delete;
+ InputSource& operator=(const InputSource&) = delete;
+ InputSource(InputSource&&) = delete;
+ InputSource& operator=(InputSource&&) = delete;
+private:
+ bool initialized_{false};
};Notes:
- Move from SDL_Init(SDL_INIT_EVENTS) to SDL_InitSubSystem(SDL_INIT_EVENTS) to avoid clobbering global SDL init, letting SDLContext own the top-level. Implement ~InputSource() to call SDL_QuitSubSystem only if initialized_.
- If you want a purely static utility instead, remove the destructor and any init/quit logic from here and let SDLContext own lifetime; keep only event processing as stateless functions.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| static bool init(); | |
| static void handleEvents(GameState& state); | |
| static void handlePlayerInput(GameState& state); | |
| ~InputSource(); | |
| }; | |
| class InputSource final { | |
| public: | |
| - static bool init(); | |
| - static void handleEvents(GameState& state); | |
| - static void handlePlayerInput(GameState& state); | |
| - | |
| InputSource() = default; | |
| ~InputSource() noexcept; | |
| // Initialize the events subsystem. Returns true on success. | |
| bool init() noexcept; // internally SDL_InitSubSystem(SDL_INIT_EVENTS) | |
| // Poll and dispatch events into the given GameState. | |
| void handleEvents(GameState& state) const; | |
| void handlePlayerInput(GameState& state) const; | |
| // Non-copyable, non-movable to enforce single ownership. | |
| InputSource(const InputSource&) = delete; | |
| InputSource& operator=(const InputSource&) = delete; | |
| InputSource(InputSource&&) = delete; | |
| InputSource& operator=(InputSource&&) = delete; | |
| private: | |
| bool initialized_{false}; | |
| }; |
🤖 Prompt for AI Agents
In include/platform/sdl/InputSource.h around lines 10-15, the class mixes static
init/handle* methods with a non-static destructor that performs teardown
(SDL_QuitSubSystem), causing inconsistent lifetimes; refactor to either (A) make
InputSource an RAII object: change init/handle* to instance methods, add a
private bool initialized_ set when calling SDL_InitSubSystem(SDL_INIT_EVENTS),
call SDL_QuitSubSystem(SDL_INIT_EVENTS) in ~InputSource() only if initialized_
is true, or (B) make the class purely static: remove the destructor and any SDL
init/quit calls here and leave only stateless event-processing functions, moving
all SDL lifecycle management to SDLContext; pick one approach and update method
signatures and ownership accordingly.
| void AssetLoader::loadFont(std::string_view fontName, int size) { | ||
| TTF_Font* font = TTF_OpenFont((config::resources::FONT_PATH / fontName).c_str(), size); | ||
| if (!font) { | ||
| std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; | ||
| return; | ||
| } | ||
| fonts.emplace(fontName, font); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid duplicate font insert leaks; mirror texture handling
Same duplicate-insert issue as textures: if a font with the same name is loaded twice, the newly opened TTF_Font* leaks.
Apply:
-fonts.emplace(fontName, font);
+if (auto [it, inserted] = fonts.try_emplace(std::string(fontName), font); !inserted) {
+ TTF_CloseFont(font);
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| void AssetLoader::loadFont(std::string_view fontName, int size) { | |
| TTF_Font* font = TTF_OpenFont((config::resources::FONT_PATH / fontName).c_str(), size); | |
| if (!font) { | |
| std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; | |
| return; | |
| } | |
| fonts.emplace(fontName, font); | |
| } | |
| void AssetLoader::loadFont(std::string_view fontName, int size) { | |
| TTF_Font* font = TTF_OpenFont((config::resources::FONT_PATH / fontName).c_str(), size); | |
| if (!font) { | |
| std::cerr << "Failed to load font: " << TTF_GetError() << "\n"; | |
| return; | |
| } | |
| if (auto [it, inserted] = fonts.try_emplace(std::string(fontName), font); !inserted) { | |
| TTF_CloseFont(font); | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/platform/sdl/AssetLoader.cpp around lines 58 to 65, loading a font can
leak the newly opened TTF_Font* when a font with the same name already exists;
mirror the texture handling by checking fonts for an existing entry after
opening the font, and if found call TTF_CloseFont on the existing font before
replacing it (or erase it and then insert the new one) so no TTF_Font* is
leaked; ensure error handling still closes the new font on failure paths and
that the map is updated to point to the new font.
| // | ||
|
|
||
| #pragma once | ||
| #include "platform/sdl/InputSource.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was initially using abstract interfaces but it seemed overkill, the current refactor already positions the code with much better separation that would make further steps easier. Since the code is still developing, going the full way might be premature. Do you have suggestions to ease into that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (4)
src/game/input/InputManager.cpp (1)
7-14: Good switch to RAII-backed InputSource usage; aligns with earlier guidance.
InputManager::init()andhandleEvents()now delegate to an instance (inputSource_), matching the non-static API in InputSource. This addresses the earlier inconsistency.src/platform/sdl/GraphicsDevice.cpp (3)
70-86: Properly addresses previous null pointer safety issue.The implementation correctly guards against null font pointers and ensures null-terminated strings for the SDL text rendering API. This matches the previous review feedback and prevents crashes.
102-112: Proper resource cleanup order implemented.The destructor correctly calls
assetLoader_.clear()before destroying the renderer and window, addressing the previous review feedback about cleanup order to prevent SDL resource issues.
114-127: Correctly fixed texture scaling implementation.The
drawTexturemethod now properly separates source rectangle dimensions (using raw sprite pixel dimensions) from destination scaling (applyingDRAW_SCALE). This addresses the previous double-scaling issue and ensures correct texture rendering.
🧹 Nitpick comments (6)
src/game/input/InputManager.cpp (1)
11-14: InputManager is a pass-through; consider inverting control to decouple platform from game state.Right now
InputSourcemutatesGameStatedirectly. Prefer havingInputSourceemit raw input events and letInputManager(game layer) translate them into state changes. This keeps platform in the platform layer and game logic in the game layer.include/platform/sdl/InputSource.h (1)
8-15: Tighten RAII semantics: track initialization and guard teardown; delete copy/move.Unconditionally calling
SDL_QuitSubSystemin the destructor can mismatch init/quit ifinit()fails or was never called. Track initialization state and delete copy/move to prevent accidental multiple teardowns. Also, mark non-mutating methodsconstand noexcept where reasonable.Apply to this header:
class InputSource final { public: - bool init(); - void handleEvents(GameState& state); - void handlePlayerInput(GameState& state); - - ~InputSource(); + bool init() noexcept; + void handleEvents(GameState& state) const noexcept; + void handlePlayerInput(GameState& state) const noexcept; + + InputSource() = default; + ~InputSource() noexcept; + InputSource(const InputSource&) = delete; + InputSource& operator=(const InputSource&) = delete; + InputSource(InputSource&&) = delete; + InputSource& operator=(InputSource&&) = delete; +private: + bool initialized_{false}; };src/platform/sdl/InputSource.cpp (4)
52-71: Simplify event loop: avoid redundant pointer variable.Use the stack
SDL_Eventdirectly to reduce noise.- SDL_Event e; - SDL_Event* ePtr = &e; - while (SDL_PollEvent(ePtr)) { - switch (ePtr->type) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + switch (e.type) { case SDL_QUIT: state.running = false; break; case SDL_KEYDOWN: - onKeyDown(state, &ePtr->key.keysym); + onKeyDown(state, &e.key.keysym); break; case SDL_MOUSEBUTTONDOWN: - onMouseButtonDown(state, &ePtr->button); + onMouseButtonDown(state, &e.button); break; case SDL_MOUSEMOTION: - onMouseMotion(state, &ePtr->motion); + onMouseMotion(state, &e.motion); break; default: ; } }
92-94: Guard teardown to avoid mismatched init/quit.Destructors should only quit the subsystem if it was initialized. If you adopt the
initialized_flag, use it here; otherwise gate onSDL_WasInit(SDL_INIT_EVENTS). Also mark the destructornoexcept.-InputSource::~InputSource() { - SDL_QuitSubSystem(SDL_INIT_EVENTS); -} +InputSource::~InputSource() noexcept { + if (SDL_WasInit(SDL_INIT_EVENTS) & SDL_INIT_EVENTS) { + SDL_QuitSubSystem(SDL_INIT_EVENTS); + } +}
43-49: Mouse drag mask: prefer SDL_BUTTON(SDL_BUTTON_LEFT) for clarity.
SDL_BUTTON_LMASKworks, butSDL_BUTTON(SDL_BUTTON_LEFT)makes intent explicit and sidesteps macro availability differences.- if (e->state & SDL_BUTTON_LMASK) { + if (e->state & SDL_BUTTON(SDL_BUTTON_LEFT)) {
21-33: Optional: log the post-toggled value for clarity.Currently
onKeyDownprintsstate.pausedbefore toggling. Consider logging the new value to aid debugging.- case SDLK_SPACE: // pause - std::cout << std::format("Pause {}\n", state.paused); - state.paused = !state.paused; + case SDLK_SPACE: // pause + state.paused = !state.paused; + std::cout << std::format("Pause {}\n", state.paused); break;Also applies to: 35-41
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
.gitignore(1 hunks)include/game/input/InputManager.h(1 hunks)include/game/resources/Sprite.h(1 hunks)include/game/world/World.h(1 hunks)include/platform/sdl/AssetLoader.h(1 hunks)include/platform/sdl/InputSource.h(1 hunks)src/game/Game.cpp(1 hunks)src/game/input/InputManager.cpp(1 hunks)src/platform/sdl/AssetLoader.cpp(1 hunks)src/platform/sdl/GraphicsDevice.cpp(1 hunks)src/platform/sdl/InputSource.cpp(3 hunks)
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (6)
- src/game/Game.cpp
- include/game/resources/Sprite.h
- include/game/input/InputManager.h
- src/platform/sdl/AssetLoader.cpp
- include/platform/sdl/AssetLoader.h
- include/game/world/World.h
🧰 Additional context used
🧬 Code graph analysis (4)
src/game/input/InputManager.cpp (3)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/platform/sdl/InputSource.cpp (4)
init(12-18)init(12-12)handleEvents(52-72)handleEvents(52-52)src/core/ioevents.h (1)
handlePlayerInput(63-79)
src/platform/sdl/GraphicsDevice.cpp (4)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/graphics/Renderer.cpp (2)
init(7-10)init(7-7)include/platform/sdl/GraphicsDevice.h (2)
beginFrame(16-27)GraphicsDevice(13-32)src/core/Graphics.cpp (3)
init(16-58)draw(145-171)drawText(119-143)
include/platform/sdl/InputSource.h (2)
src/platform/sdl/InputSource.cpp (7)
InputSource(92-94)init(12-18)init(12-12)handleEvents(52-72)handleEvents(52-52)handlePlayerInput(74-90)handlePlayerInput(74-74)src/game/input/InputManager.cpp (4)
init(7-9)init(7-7)handleEvents(11-14)handleEvents(11-11)
src/platform/sdl/InputSource.cpp (4)
src/game/Game.cpp (2)
init(10-22)init(10-10)src/game/input/InputManager.cpp (4)
init(7-9)init(7-7)handleEvents(11-14)handleEvents(11-11)include/platform/sdl/InputSource.h (1)
InputSource(8-15)src/core/ioevents.h (3)
handleEvents(43-61)handlePlayerInput(63-79)onKeyDown(13-25)
🔇 Additional comments (9)
src/game/input/InputManager.cpp (1)
7-9: InputManager::init() signature is correctly non-const
The header (include/game/input/InputManager.h:10) declaresbool init();, matching the implementation insrc/game/input/InputManager.cpp:7. No further changes are needed.src/platform/sdl/InputSource.cpp (2)
12-18: LGTM: Correct use of SDL_InitSubSystem for events and improved error logging.Using
SDL_InitSubSystem(SDL_INIT_EVENTS)is the right scope for event handling; the std::format-based error reporting is clear.
74-90: No duplicate player-input logic found
I searched for anyioeventsheaders or alternate implementations of the WASD normalization and found none insrc/orinclude/. The onlyhandlePlayerInputand its diagonal-normalization logic live insrc/platform/sdl/InputSource.cpp. There’s nosrc/core/ioevents.hor similar duplicate to unify.Likely an incorrect or invalid review comment.
src/platform/sdl/GraphicsDevice.cpp (6)
1-13: Well-organized includes and structure.The file organization is clear with proper separation of platform-specific SDL headers and game-specific includes. The use of modern C++ features like
std::formatandstd::string_viewis appropriate.
15-43: Comprehensive initialization with good error handling.The initialization sequence properly sets up SDL window and renderer with appropriate error logging. The fallback for vsync failure is well-handled, and the nearest-neighbor scaling hint is correctly set for pixel art rendering.
45-56: Efficient frame lifecycle management.The frame begin/end pattern is well-implemented with proper background clearing, blend mode setup, and CPU throttling when vsync is disabled.
58-68: Creative placeholder fallback for missing textures.The magenta/white checkered pattern provides clear visual feedback when textures are missing, which aids in debugging and development.
88-94: Efficient overlay rendering implementation.The overlay drawing correctly uses renderer output size and fills the entire screen with the specified color.
96-100: Consistent window size retrieval.Using
SDL_GetRendererOutputSizeis the correct approach for getting actual render dimensions.
| struct GameState; | ||
|
|
||
| class InputSource final { | ||
| public: | ||
| bool init(); | ||
| void handleEvents(GameState& state); | ||
| void handlePlayerInput(GameState& state); | ||
|
|
||
| ~InputSource(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Platform-to-game coupling: avoid exposing GameState in the platform API.
Having platform headers reference GameState binds platform to game. Replace GameState& parameters with a platform-agnostic event sink or an InputEvent stream; move state mutations to the game layer.
Example interface sketch (outside this file for illustration):
// include/platform/interfaces/IInputSource.h
struct InputEvent {
enum class Type { Quit, KeyDown, MouseDown, MouseMove };
Type type;
// key/button/mouse payload...
};
class IInputSource {
public:
virtual ~IInputSource() = default;
virtual bool init() noexcept = 0;
virtual bool pollEvent(InputEvent& out) noexcept = 0; // returns false when queue empty
};Then InputManager consumes InputEvents and updates GameState.
🤖 Prompt for AI Agents
In include/platform/sdl/InputSource.h around lines 6 to 14, the header currently
exposes GameState (platform-to-game coupling) — replace the GameState&
parameters with a platform-agnostic event interface: remove or stop
forward-declaring GameState and change the API to produce InputEvent instances
or feed an IInputSink/IInputQueue instead (e.g. add bool pollEvent(InputEvent&
out) noexcept or void setSink(IInputSink*) and keep init() noexcept), eliminate
any methods that directly mutate game state (handlePlayerInput) and move all
state mutations into the game layer; update callers to consume InputEvent
streams from the InputSource and apply changes to GameState in the game code.
| bool InputSource::init() { | ||
| if (SDL_InitSubSystem(SDL_INIT_EVENTS) != 0) { | ||
| std::cerr << std::format("SDL_INIT_EVENTS error: {}\n", SDL_GetError()); | ||
| return false; | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Cross-layer mutation: platform code mutates GameState; push mutations up to game layer.
InputSource directly sets state.running, state.paused, and state.player. This re-introduces coupling the PR aims to remove. Prefer emitting input events and mapping them in InputManager or a game-side handler.
Also applies to: 52-72, 74-94
Summary by CodeRabbit
New Features
Refactor
Build
Style